package cheesymock;

import java.lang.reflect.*;

public abstract class Mock {
	private Class<?> interfaceToImplement;
	private Validator[] validators;
	private Object delegate;
	private DefaultReturn defaultReturn;
	private final static Object DELEGATE_NO_RETURN_VALUE = new Object();
	
	public Mock(Class<?> interfaceToImplement, Validator[] validators, Object delegate, 
			DefaultReturn defaultReturn) {
		for(Validator v: validators) {
		    v.creation(interfaceToImplement, delegate);
		}
		this.interfaceToImplement = interfaceToImplement;
		this.validators = validators;
		this.delegate = delegate;
		this.defaultReturn = defaultReturn;
	}

	protected Object doInvoke(Object proxy, Method method, Object[] arguments) throws Throwable {
		if ("equals".equals(method.getName())) {
		    return proxy == arguments[0];
		}
		for(Validator v: validators) {
		    v.invocation(method, arguments);
		}
		Object delegateResult = invokeDelegate(delegate, method, arguments);
		if ( delegateResult == DELEGATE_NO_RETURN_VALUE &&
		     delegate instanceof WrappingDelegate ) {
		    Field f = WrappingDelegate.class.getDeclaredField("_target");
		    f.setAccessible(true);
		    Object _target = f.get(delegate);
		    if ( _target != null ) {
		        delegateResult = invokeDelegate(_target, method, arguments);
		    }
		}
		if ( delegateResult != DELEGATE_NO_RETURN_VALUE ) return delegateResult;
		if (defaultReturn != null) {
		    return defaultReturn.value(method);
		}
		return null;
	}
    
	public void check() {
		for(Validator v: validators) {
			try {
				v.check();
			}
			catch (RuntimeException e) {
				Throwable t = e.getCause();
				if (t instanceof RuntimeException) {
				    throw (RuntimeException) t;
				}
				throw e;
			}
		}
	}
	
	private Object invokeDelegate(Object delegate, Method method, Object[] arguments) throws Throwable {
        try {
            Method m = MethodUtil.resolveMethod(method, delegate);
            m.setAccessible(true);
            return m.invoke(delegate != null ? delegate : this, arguments);
        }
        catch (NoSuchMethodException nse) {
        }
        try {
            Field f = MethodUtil.resolveProperty(method, delegate);
			f.setAccessible(true);
            if (MethodUtil.isGetter(method, arguments)) {
                return f.get(delegate);
            }
            else {
                f.set(delegate, arguments[0]);
                return null;
            }
        }
        catch (NoSuchFieldException nsfe) {
        }
        return DELEGATE_NO_RETURN_VALUE;
	}
	
}